﻿#include "SerialPortUtils.h"
#include <Windows.h>
#include <cfgmgr32.h>
#include <setupapi.h>
#include <boost/algorithm/string.hpp>
#pragma comment(lib,"setupapi.lib")
#pragma comment(lib,"cfgmgr32.lib")

namespace {
    bool GetDevRegPropString(HDEVINFO hDevInfo, SP_DEVINFO_DATA* devInfo, DWORD propType, std::wstring& propVal) {
        wchar_t buffer[MAX_PATH]{};
        DWORD propValType = 0;
        BOOL ret = ::SetupDiGetDeviceRegistryPropertyW(
            hDevInfo, devInfo, propType, &propValType, reinterpret_cast<BYTE*>(buffer), sizeof(buffer), nullptr
        );
        if (!ret || propValType != REG_SZ) {
            return false; // error or reg type dismatch
        }
        propVal = buffer;
        return true;
    }

    bool EnumDevices(HDEVINFO hDevInfo, std::vector<util::ser::SerialPortInfo>& devs) {
        devs.clear();
        for (DWORD i = 0; i < 100; i++) {
            SP_DEVINFO_DATA devInfo{};
            devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
            if (!::SetupDiEnumDeviceInfo(hDevInfo, i, &devInfo)) {
                if (GetLastError() == ERROR_NO_MORE_ITEMS) {
                    break; // end of loop
                }
                return false;
            }
            wchar_t hwid[MAX_DEVICE_ID_LEN + 1]{};
            if (::CM_Get_Device_IDW(devInfo.DevInst, hwid, _countof(hwid), 0) != CR_SUCCESS) {
                continue;
            }

            util::ser::SerialPortInfo portInfo{};
            portInfo.devInstID = hwid;
            std::wstring frdName;
            GetDevRegPropString(hDevInfo, &devInfo, SPDRP_FRIENDLYNAME, frdName);
            if (frdName.empty() || !boost::icontains(frdName, L"(COM")) {
                continue;
            }
            portInfo.friendlyName = frdName;
            GetDevRegPropString(hDevInfo, &devInfo, SPDRP_DEVICEDESC, portInfo.devDesc);
            GetDevRegPropString(hDevInfo, &devInfo, SPDRP_SERVICE, portInfo.devService);

            ULONG status = 0, problem = 0;
            ::CM_Get_DevNode_Status(&status, &problem, devInfo.DevInst, 0);
            if (status & DN_HAS_PROBLEM) {
                portInfo.devProblemNum = problem;
            }
            if (portInfo.GetComPath().empty()) {
                continue;
            }
            devs.emplace_back(portInfo);
        }
        return true;
    }
}

namespace util::ser {
    std::wstring SerialPortInfo::GetComPath() {
        if (!comPath.empty()) {
            return comPath;
        }
        if (friendlyName.empty()) {
            return {};
        }
        auto str = boost::to_upper_copy(friendlyName);
        auto idx = str.rfind(L"(COM");
        if (idx == str.npos) {
            return {};
        }
        auto idx2 = str.find(L')', idx);
        if (idx2 == str.npos) {
            return {};
        }
        str = str.substr(idx + 4, idx2 - idx - 4);
        if (str.find_first_not_of(L"0123456789") != str.npos) {
            return {};
        }
        comPath = L"\\\\.\\COM" + str;
        comPortNum = std::wcstoul(str.c_str(), nullptr, 10);
        return comPath;
    }

    std::vector<SerialPortInfo> EnumCurrentSerialPorts() {
        DWORD cnt = 1;
        GUID clsPorts{};
        if (!::SetupDiClassGuidsFromNameW(L"Ports", &clsPorts, 1, &cnt) || !cnt) {
            return {};
        }
        HDEVINFO hDevInfo = ::SetupDiGetClassDevsW(&clsPorts, nullptr, 0, DIGCF_PRESENT);
        if (hDevInfo == INVALID_HANDLE_VALUE) {
            return {};
        }
        std::vector<util::ser::SerialPortInfo> devs;
        EnumDevices(hDevInfo, devs);
        ::SetupDiDestroyDeviceInfoList(hDevInfo);
        return devs;
    }
}